#include "drv_lcd.h"
#include "bsp.h"
#include "bsp_spi.h"


/* ST7796 LCD Driver (SPI) */


/* IO define */
/**
 * LCD_CS       PE1
 * LCD_DC       PE0
 * LCD_SCL      PC10-SPI3_SCK
 * LCD_SDA      PC12-SPI3_MOSI
 * LCD_BL       PB5
 * LCD_RESET    mcu_reset
 * 
 */

#define LCD_IO_SPI_HW

#define LCD_IO_CS_SET()     bsp_gpio_pin_set(GPIOE, GPIO_PIN_1)
#define LCD_IO_CS_CLR()     bsp_gpio_pin_clr(GPIOE, GPIO_PIN_1)

#define LCD_IO_DC_SET()     bsp_gpio_pin_set(GPIOE, GPIO_PIN_0)
#define LCD_IO_DC_CLR()     bsp_gpio_pin_clr(GPIOE, GPIO_PIN_0)

#define LCD_IO_BL_SET()     bsp_gpio_pin_set(GPIOB, GPIO_PIN_5)
#define LCD_IO_BL_CLR()     bsp_gpio_pin_clr(GPIOB, GPIO_PIN_5)

#ifdef LCD_IO_SPI_HW
  #define st7796_io_write_byte(x)       bsp_spi3_write_byte(x)
  #define st7796_io_write_buff(x,l)     bsp_spi3_write_buff(x,l)
  #define st7796_io_lock()              bsp_spi3_lock()
  #define st7796_io_unlock()            bsp_spi3_unlock()
#else
  #define st7796_io_write_byte(x)       drv_lcd_write_bus(x)
  #define st7796_io_write_buff(x,l)     drv_lcd_write_buff(x,l)
  #define st7796_io_lock()
  #define st7796_io_unlock()
#endif

/** commands of ST7796 */
#define LCD_CMD_SWRESET 0x01  // Software Reset
#define LCD_CMD_RDDID   0x04  // Read Display ID
#define LCD_CMD_INVOFF  0x20  // Display Inversion Off
#define LCD_CMD_INVON   0x21  // Display Inversion On
#define LCD_CMD_CASET   0x2A  // Column Address Set
#define LCD_CMD_RASET   0x2B  // Row Address Set
#define LCD_CMD_RAMWR   0x2C  // Memory Writ
#define LCD_CMD_RAMRD   0x2E  // Memory Read
#define LCD_CMD_MADCTL  0x36  // Memory Data Access Control

/* MADCTL Defines */
#define MADCTL_MY  0x80
#define MADCTL_MX  0x40
#define MADCTL_MV  0x20
#define MADCTL_ML  0x10
#define MADCTL_RGB 0x08
#define MADCTL_MH  0x04

/* static var */

static _drv_lcd_info_t lcd_info_t =
{
    .width = CONFIG_HW_LCD_WIDTH,
    .height = CONFIG_HW_LCD_HEIGHT,
    .dir = LCD_DIR_BTRL,
    // .dir = LCD_DIR_LRTB,
    .name = "ST7796",
};


/* static func */

#ifndef LCD_IO_SPI_HW
static __INLINE void drv_lcd_write_bus(uint8_t dat)
{
    uint8_t i;

    // LCD_IO_CS_CLR();
    for (i = 0; i < 8; i++)
    {
        // LCD_IO_SCL = 0;
        bsp_gpio_pin_clr(GPIOC, GPIO_PIN_10);
        if (dat & 0x80)
        {
            // LCD_IO_SDA = 1;
            bsp_gpio_pin_set(GPIOC, GPIO_PIN_12);
        }
        else
        {
            // LCD_IO_SDA = 0;
            bsp_gpio_pin_clr(GPIOC, GPIO_PIN_12);
        }
        // LCD_IO_SCL = 1;
        bsp_gpio_pin_set(GPIOC, GPIO_PIN_10);
        dat <<= 1;
    }
    // LCD_IO_CS_SET();
}


static void drv_lcd_write_buff(uint8_t *data, uint16_t size)
{
    uint8_t *p = data;
    while (size--)
    {
        drv_lcd_write_bus(*p);
        p++;
    }
}
#endif

static __INLINE void st7796_delay(uint16_t t)
{
#if 1
    delay_ms(t);
#else
    int i;
    while (t--)
    {
        i = 0xFFFF;
        while (i--) ;
    }

#endif
}

static __INLINE void st7796_bl_on(void)
{
    LCD_IO_BL_SET();
}

static __INLINE void st7796_bl_off(void)
{
    LCD_IO_BL_CLR();
}

static __INLINE void st7796_wr_reg(uint8_t reg)
{
    LCD_IO_CS_CLR();
    LCD_IO_DC_CLR();    // cmd
    st7796_io_write_byte(reg);
    LCD_IO_CS_SET();
}

static __INLINE void st7796_wr_data(uint8_t data)
{
    LCD_IO_CS_CLR();
    LCD_IO_DC_SET();    // data
    st7796_io_write_byte(data);
    LCD_IO_CS_SET();
}

// static __INLINE void st7796_write_reg(uint8_t reg, uint8_t data)
// {
//     LCD_IO_CS_CLR();
//     LCD_IO_DC_CLR();    // cmd
//     st7796_io_write_byte(reg);
//     LCD_IO_CS_SET();
//     reg++;
//     LCD_IO_CS_CLR();
//     LCD_IO_DC_SET();    // data
//     st7796_io_write_byte(data);
//     LCD_IO_CS_SET();
// }

static __INLINE void st7796_write_reg_buff(uint8_t reg, uint8_t *data, uint16_t size)
{
    LCD_IO_DC_CLR();    // cmd
    LCD_IO_CS_CLR();
    st7796_io_write_byte(reg);
    LCD_IO_CS_SET();
    // reg++;
    LCD_IO_DC_SET();    // data
    LCD_IO_CS_CLR();
    st7796_io_write_buff(data, size);
    LCD_IO_CS_SET();
}

static void st7796_set_window_area(uint16_t xs, uint16_t ys, uint16_t xe, uint16_t ye)
{
    uint8_t buff[4];

    // x
    if (xe >= lcd_info_t.width) xe = lcd_info_t.width - 1;
    buff[0] = (uint8_t)(xs >> 8);
    buff[1] = (uint8_t)(xs & 0xff);
    buff[2] = (uint8_t)(xe >> 8);
    buff[3] = (uint8_t)(xe & 0xff);
    st7796_write_reg_buff(LCD_CMD_CASET, buff, sizeof(buff));

    // y
    if (ye >= lcd_info_t.height) ye = lcd_info_t.height - 1;
    buff[0] = (uint8_t)(ys >> 8);
    buff[1] = (uint8_t)(ys & 0xff);
    buff[2] = (uint8_t)(ye >> 8);
    buff[3] = (uint8_t)(ye & 0xff);
    st7796_write_reg_buff(LCD_CMD_RASET, buff, sizeof(buff));
}

/* static callback func */

static int st7796_init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct;

    __HAL_RCC_GPIOB_CLK_ENABLE();
    __HAL_RCC_GPIOE_CLK_ENABLE();

    /* PB5 - LCD_BL */
    GPIO_InitStruct.Pin = GPIO_PIN_5;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

    /* PE1 - LCD_CS */
    GPIO_InitStruct.Pin = GPIO_PIN_1;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);

    /* PE0 - LCD_DC */
    GPIO_InitStruct.Pin = GPIO_PIN_0;
    HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);

    /* SPI3 */
#ifdef LCD_IO_SPI_HW
    bsp_spi3_init();
#else
    __HAL_RCC_GPIOC_CLK_ENABLE();
    GPIO_InitStruct.Pin = GPIO_PIN_10 | GPIO_PIN_12;
    HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
#endif

    st7796_bl_off();    // 关闭背光

    /* st7796 reg config */
    st7796_wr_reg(0x01);    // soft reset
    st7796_delay(50);

    st7796_wr_reg(0x13);    // Normal Display Mode On
    st7796_wr_reg(0x11);    // Sleep Out
    st7796_wr_reg(0x38);    // Idle Mode Off    
    st7796_delay(10);
    
    st7796_wr_reg(0xf0);
    st7796_wr_data(0xc3); // enable command 2 part 1
    st7796_wr_reg(0xf0);
    st7796_wr_data(0x96); // enable command 2 part 2

    st7796_wr_reg(0x36);  //内存数据访问控制
    st7796_wr_data(0x28);

    st7796_wr_reg(0x3a); // 16bit pixel
    st7796_wr_data(0x05);   // 0x55

    st7796_wr_reg(0xb4);
    st7796_wr_data(0x01);

    st7796_wr_reg(0xb7);
    st7796_wr_data(0xc6);

    st7796_wr_reg(0xe8);
    st7796_wr_data(0x40);
    st7796_wr_data(0x8a);
    st7796_wr_data(0x00);
    st7796_wr_data(0x00);
    st7796_wr_data(0x29);
    st7796_wr_data(0x19);
    st7796_wr_data(0xa5);
    st7796_wr_data(0x33);

    st7796_wr_reg(0xc1);
    st7796_wr_data(0x06);
    st7796_wr_reg(0xc2);
    st7796_wr_data(0xa7);
    st7796_wr_reg(0xc5);
    st7796_wr_data(0x18);

    st7796_wr_reg(0xe0);    // Positive Voltage Gamma Coltrol
    st7796_wr_data(0xf0);
    st7796_wr_data(0x09);
    st7796_wr_data(0x0b);
    st7796_wr_data(0x06);
    st7796_wr_data(0x04);
    st7796_wr_data(0x15);
    st7796_wr_data(0x2f);
    st7796_wr_data(0x54);
    st7796_wr_data(0x42);
    st7796_wr_data(0x3c);
    st7796_wr_data(0x17);
    st7796_wr_data(0x14);
    st7796_wr_data(0x18);
    st7796_wr_data(0x1b);
    
    st7796_wr_reg(0xe1);    // Negative Voltage Gamma Coltrol
    st7796_wr_data(0xf0);
    st7796_wr_data(0x09);
    st7796_wr_data(0x0b);
    st7796_wr_data(0x06);
    st7796_wr_data(0x04);
    st7796_wr_data(0x03);
    st7796_wr_data(0x2d);
    st7796_wr_data(0x43);
    st7796_wr_data(0x42);
    st7796_wr_data(0x3b);
    st7796_wr_data(0x16);
    st7796_wr_data(0x14);
    st7796_wr_data(0x17);
    st7796_wr_data(0x1b);

    st7796_wr_reg(0xf0);
    st7796_wr_data(0x3c);
    st7796_wr_reg(0xf0);
    st7796_wr_data(0x69);
    
    st7796_wr_reg(0x21); //Display Inversion On
    st7796_wr_reg(0x29); // Display ON
    st7796_delay(50);

    return 0;
}

static int st7796_deinit(void)
{
    return 0;
}

static int st7796_set_backlight(uint8_t val)
{
    if (val) st7796_bl_on();
    else st7796_bl_off();
    return 0;
}

static int st7796_set_direction(_drv_lcd_dir_t dir)
{
    uint8_t reg_data = 0;
    reg_data |= MADCTL_RGB;

    switch (dir)
    {
    case LCD_DIR_LRTB: // 左到右，上到下
        break;
    case LCD_DIR_LRBT: // 左到右，下到上
        reg_data |= MADCTL_MY;
        break;
    case LCD_DIR_RLTB: // 右到左，上到下
        reg_data |= MADCTL_MX;
        break;
    case LCD_DIR_RLBT: // 右到左，下到上
        reg_data |= MADCTL_MX | MADCTL_MY;
        break;
    case LCD_DIR_TBLR: // 上到下，左到右
        reg_data |= MADCTL_MV;
        break;
    case LCD_DIR_BTLR: // 下到上，左到右
        reg_data |= MADCTL_MY | MADCTL_MV;
        break;
    case LCD_DIR_TBRL: // 上到下，右到左
        reg_data |= MADCTL_MX | MADCTL_MV;
        break;
    case LCD_DIR_BTRL: // 下到上，右到左
        reg_data |= MADCTL_MX | MADCTL_MY | MADCTL_MV;
        break;
    default:
        return -1;
    }

    st7796_io_lock();
    st7796_wr_reg(LCD_CMD_MADCTL);
    st7796_wr_data(reg_data);
    st7796_io_unlock();
    lcd_info_t.dir = dir;

    return 0;
}

static int st7796_set_window(uint16_t posx, uint16_t posy, uint16_t width, uint16_t height)
{
    // x
    width = posx + width - 1;
    if (width >= lcd_info_t.width) width = lcd_info_t.width - 1;

    // y
    height = posy + height - 1;
    if (height >= lcd_info_t.height) height = lcd_info_t.height - 1;
    st7796_set_window_area(posx, posy, width, height);

    return 0;
}

static int st7796_draw_pixel(uint16_t posx, uint16_t posy, lcd_color_t color)
{
    uint8_t buff[2];
 
    buff[0] = (uint8_t)(posx >> 8);
    buff[1] = (uint8_t)(posx & 0xff);
    st7796_write_reg_buff(LCD_CMD_CASET, buff, sizeof(buff));
    buff[0] = (uint8_t)(posy >> 8);
    buff[1] = (uint8_t)(posy & 0xff);
    st7796_write_reg_buff(LCD_CMD_RASET, buff, sizeof(buff));

    st7796_wr_reg(LCD_CMD_RAMWR);

    buff[0] = (uint8_t)(color >> 8);
    buff[1] = (uint8_t)(color & 0xff);

    LCD_IO_DC_SET();
    LCD_IO_CS_CLR();
    st7796_io_write_buff(buff, sizeof(buff));
    LCD_IO_CS_SET();

    return 0;
}

static int st7796_fill_color(uint16_t posx, uint16_t posy, uint16_t width, uint16_t height, lcd_color_t color)
{
    uint32_t tmp;
    uint8_t ch, cl;

    ch = color >> 8;
    cl = color & 0xff;
    tmp = width * height;

    st7796_io_lock();
    st7796_set_window(posx, posy, width, height);
    st7796_wr_reg(LCD_CMD_RAMWR);
    
    LCD_IO_DC_SET();
    LCD_IO_CS_CLR();
    while (tmp--)
    {
        st7796_io_write_byte(ch);
        st7796_io_write_byte(cl);
    }
    LCD_IO_CS_SET();
    st7796_io_unlock();

    return 0;
}

static int st7796_fill_bitmap(uint16_t posx, uint16_t posy, uint16_t width, uint16_t height, lcd_color_t *bitmap)
{
    st7796_io_lock();
    st7796_set_window(posx, posy, width, height);
    st7796_write_reg_buff(LCD_CMD_RAMWR, (uint8_t*)bitmap, width*height*sizeof(lcd_color_t));
    st7796_io_unlock();

    return 0;
}

static int st7796_fill_bitmap_area(uint16_t posx0, uint16_t posy0, uint16_t posx1, uint16_t posy1, lcd_color_t *bitmap)
{
    uint32_t size;

    size = (posx1-posx0+1) * (posy1-posy0+1) * 2;

    st7796_io_lock();

    st7796_set_window_area(posx0, posy0, posx1, posy1); // set window
    st7796_wr_reg(LCD_CMD_RAMWR);   // write ram cmd

    LCD_IO_DC_SET();
    LCD_IO_CS_CLR();
    bsp_spi3_set_speed(SPI_BAUDRATEPRESCALER_2);
    bsp_spi3_write_buff_dma((uint8_t*)bitmap, size);
    // st7796_io_write_buff((uint8_t*)bitmap, size);
    bsp_spi3_set_speed(SPI_BAUDRATEPRESCALER_4);
    LCD_IO_CS_SET();

    st7796_io_unlock();

    return 0;
}

static int st7796_get_info(_drv_lcd_info_t *info)
{
    memcpy(info, &lcd_info_t, sizeof(_drv_lcd_info_t));

    return 0;
}



/* export var */


_drv_lcd_opt_t lcd_opt_st7796_t = 
{
    .init = st7796_init,
    .deinit = st7796_deinit,
    .set_backlight = st7796_set_backlight,
    .set_direction = st7796_set_direction,
    .set_window = st7796_set_window,
    .draw_pixel = st7796_draw_pixel,
    .fill_color = st7796_fill_color,
    .fill_bitmap = st7796_fill_bitmap,
    .fill_bitmap_area = st7796_fill_bitmap_area,
    .get_info = st7796_get_info,
};




